-module(server).
-behaviour(gen_server).

-export([init/1, handle_call/3, handle_cast/2,
         handle_info/2, terminate/2, code_change/3]).

-export([start/1, start/3, start/4, stop/1, test/0]).

-include("common.hrl").
-include("pp.hrl").

-record(server, {
                 port,
                 host,
                 test_mode
                }).

-record(client, {
                 server = none,
                 player = none
                }).

start([Node,Port, Host])
  when is_atom(Port),
       is_atom(Host) ->
    Port1 = list_to_integer(atom_to_list(Port)),
    Host1 = atom_to_list(Host),
    start(Node,Host1, Port1).

start(Node,Host, Port) ->
    start(Node,Host, Port, false).

start(Node,Host, Port, TestMode) ->
    case net_adm:ping(Node) of
        pong ->
            io:format("Starting  game servers...~n"),
            pg2:which_groups(),
            timer:sleep(100),
            pg2:which_groups(),
            case gen_server:start(server, [Host, Port, TestMode], []) of
                {ok, Pid} ->
                    %%io:format("server:start: pid ~w~n", [Pid]),           
                    ok = pg2:join(?GAME_SERVERS, Pid),
                    {ok, Pid};
                Result ->
                    error_logger:error_report(
                      [{module, ?MODULE},
                       {line, ?LINE},
                       {message, "Unexpected result"},
                       {call, 'gen_server:start(server)'},
                       {result, Result},
                       {port, Port},
                       {now, now()}]),
                    Result
            end;
        _ ->
            io:format("Gateway cannot found ~w, exiting.~n",
                      [Node])
    end.


init([Host, Port, TestMode]) ->
    process_flag(trap_exit, true),
    %%error_logger:logfile({open, "/tmp/"
    %% ++ atom_to_list(node())
    %% ++ ".log"}),
    Client = #client{ server = self() },
    F = fun(Sock) -> parse_packet(Sock, Client) end,
    tcp_server:stop(Port),
    {ok, _} = tcp_server:start_raw_server(Port, F, 32768, 32768),
    Server = #server{
                     host = Host,
                     port = Port,
                     test_mode = TestMode
                    },
    {ok, Server}.

stop(Server) ->
    gen_server:cast(Server, stop).

terminate(normal, Server) ->
    tcp_server:stop(Server#server.port),
    ok.

handle_cast({'BUMP', _Size}, Server) ->
    %%stats:sum(packets_in, 1),
    %%stats:sum(bytes_in, Size),
    {noreply, Server};

handle_cast({'PONG', R = #pong{}}, Server) ->
    TC = timer:now_diff(R#pong.send_time, R#pong.orig_send_time),
    TS = timer:now_diff(R#pong.recv_time, R#pong.send_time),
    stats:avg(time_to_client, TC),
    stats:avg(time_to_server, TS),
    stats:max(max_time_to_client, TC),
    stats:max(max_time_to_server, TS),
    {noreply, Server};

handle_cast(stop, Server) ->
    {stop, normal, Server};

handle_cast(Event, Server) ->
    error_logger:info_report([{module, ?MODULE},
                              {line, ?LINE},
                              {self, self()},
                              {message, Event}]),
    {noreply, Server}.


handle_call('WHERE', _From, Server) ->
    {reply, {Server#server.host, Server#server.port}, Server};
%% {ok, [{X, _, _}|_]} = inet:getif(),
%% io:format("Server address: ~w~n", [X]),
%% Host = io_lib:format("~.B.~.B.~.B.~.B",
%% [element(1, X),
%% element(2, X),
%% element(3, X),
%% element(4, X)]),
%% {reply, {Host, Server#server.port}, Server};

handle_call('USER COUNT', _From, Server) ->
    Children = tcp_server:children(Server#server.port),
    {reply, length(Children), Server};

handle_call('TEST MODE', _From, Server) ->
    {reply, Server#server.test_mode, Server};

handle_call(Event, From, Server) ->
    error_logger:info_report([{module, ?MODULE},
                              {line, ?LINE},
                              {self, self()},
                              {message, Event},
                              {from, From}]),
    {noreply, Server}.

handle_info({'EXIT', _Pid, _Reason}, Server) ->
    %% child exit?
    {noreply, Server};

handle_info(Info, Server) ->
    error_logger:info_report([{module, ?MODULE},
                              {line, ?LINE},
                              {self, self()},
                              {message, Info}]),
    {noreply, Server}.

code_change(_OldVsn, Server, _Extra) ->
    {ok, Server}.


parse_packet(Socket, Client) ->
    receive
        {tcp, Socket, Bin} ->
            Client1 = case catch pp:read(Bin) of
                          {'EXIT', Error} ->
                              error_logger:error_report(
                                [{module, ?MODULE},
                                 {line, ?LINE},
                                 {message, "Could not parse command"},
                                 {Bin, Bin},
                                 {error, Error},
                                 {now, now()}]),
                              Client;
                          #login{ nick = Nick, pass = Pass} ->
                              io:format("login nick=~w,pass=~w~n",[Nick,Pass]);
                          %%                              process_login(Client, Socket, Nick, Pass);
                          #logout{} ->
                              io:format("logout~n");
                          %%                              process_logout(Client, Socket)
                          R = #ping{} ->
                              process_ping(Client, Socket, R);
                          R = #pong{} ->
                              process_pong(Client, Socket, R)
                      
                      end,
            io:format("RCV ~p~n", [Bin]),
            
            parse_packet(Socket, Client1);
        {tcp_closed, Socket} ->
            gen_server:cast(Client#client.player, 'DISCONNECT');
        {packet, Packet} ->
            ok = ?tcpsend(Socket, Packet),
            parse_packet(Socket, Client)
    end.

%%%
%%% Handlers
%%%

%%%
%%% Utility
%%%
process_ping(Client, Socket, R) ->
    ok = ?tcpsend(Socket, #pong{ orig_send_time = R#ping.send_time }),
    Client.

process_pong(Client, _Socket, R) ->
    R1 = R#pong{ recv_time = now() },
    gen_server:cast(Client#client.server, {'PONG', R1}),
    Client.

%%
%% Test suite
%%

test() ->
    ok.